home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / perl5 / LWP / Protocol / ftp.pm < prev    next >
Encoding:
Perl POD Document  |  2008-04-11  |  17.0 KB  |  563 lines

  1. package LWP::Protocol::ftp;
  2.  
  3. # Implementation of the ftp protocol (RFC 959). We let the Net::FTP
  4. # package do all the dirty work.
  5.  
  6. use Carp ();
  7.  
  8. use HTTP::Status ();
  9. use HTTP::Negotiate ();
  10. use HTTP::Response ();
  11. use LWP::MediaTypes ();
  12. use File::Listing ();
  13.  
  14. require LWP::Protocol;
  15. @ISA = qw(LWP::Protocol);
  16.  
  17. use strict;
  18. eval {
  19.     package LWP::Protocol::MyFTP;
  20.  
  21.     require Net::FTP;
  22.     Net::FTP->require_version(2.00);
  23.  
  24.     use vars qw(@ISA);
  25.     @ISA=qw(Net::FTP);
  26.  
  27.     sub new {
  28.     my $class = shift;
  29.     LWP::Debug::trace('()');
  30.  
  31.     my $self = $class->SUPER::new(@_) || return undef;
  32.  
  33.     my $mess = $self->message;  # welcome message
  34.     LWP::Debug::debug($mess);
  35.     $mess =~ s|\n.*||s; # only first line left
  36.     $mess =~ s|\s*ready\.?$||;
  37.     # Make the version number more HTTP like
  38.     $mess =~ s|\s*\(Version\s*|/| and $mess =~ s|\)$||;
  39.     ${*$self}{myftp_server} = $mess;
  40.     #$response->header("Server", $mess);
  41.  
  42.     $self;
  43.     }
  44.  
  45.     sub http_server {
  46.     my $self = shift;
  47.     ${*$self}{myftp_server};
  48.     }
  49.  
  50.     sub home {
  51.     my $self = shift;
  52.     my $old = ${*$self}{myftp_home};
  53.     if (@_) {
  54.         ${*$self}{myftp_home} = shift;
  55.     }
  56.     $old;
  57.     }
  58.  
  59.     sub go_home {
  60.     LWP::Debug::trace('');
  61.     my $self = shift;
  62.     $self->cwd(${*$self}{myftp_home});
  63.     }
  64.  
  65.     sub request_count {
  66.     my $self = shift;
  67.     ++${*$self}{myftp_reqcount};
  68.     }
  69.  
  70.     sub ping {
  71.     LWP::Debug::trace('');
  72.     my $self = shift;
  73.     return $self->go_home;
  74.     }
  75.  
  76. };
  77. my $init_failed = $@;
  78.  
  79.  
  80. sub _connect {
  81.     my($self, $host, $port, $user, $account, $password, $timeout) = @_;
  82.  
  83.     my $key;
  84.     my $conn_cache = $self->{ua}{conn_cache};
  85.     if ($conn_cache) {
  86.     $key = "$host:$port:$user";
  87.     $key .= ":$account" if defined($account);
  88.     if (my $ftp = $conn_cache->withdraw("ftp", $key)) {
  89.         if ($ftp->ping) {
  90.         LWP::Debug::debug('Reusing old connection');
  91.         # save it again
  92.         $conn_cache->deposit("ftp", $key, $ftp);
  93.         return $ftp;
  94.         }
  95.     }
  96.     }
  97.  
  98.     # try to make a connection
  99.     my $ftp = LWP::Protocol::MyFTP->new($host,
  100.                     Port => $port,
  101.                     Timeout => $timeout,
  102.                        );
  103.     # XXX Should be some what to pass on 'Passive' (header??)
  104.     unless ($ftp) {
  105.     $@ =~ s/^Net::FTP: //;
  106.     return HTTP::Response->new(&HTTP::Status::RC_INTERNAL_SERVER_ERROR, $@);
  107.     }
  108.  
  109.     LWP::Debug::debug("Logging in as $user (password $password)...");
  110.     unless ($ftp->login($user, $password, $account)) {
  111.     # Unauthorized.  Let's fake a RC_UNAUTHORIZED response
  112.     my $mess = scalar($ftp->message);
  113.     LWP::Debug::debug($mess);
  114.     $mess =~ s/\n$//;
  115.     my $res =  HTTP::Response->new(&HTTP::Status::RC_UNAUTHORIZED, $mess);
  116.     $res->header("Server", $ftp->http_server);
  117.     $res->header("WWW-Authenticate", qq(Basic Realm="FTP login"));
  118.     return $res;
  119.     }
  120.     LWP::Debug::debug($ftp->message);
  121.  
  122.     my $home = $ftp->pwd;
  123.     LWP::Debug::debug("home: '$home'");
  124.     $ftp->home($home);
  125.  
  126.     $conn_cache->deposit("ftp", $key, $ftp) if $conn_cache;
  127.  
  128.     return $ftp;
  129. }
  130.  
  131.  
  132. sub request
  133. {
  134.     my($self, $request, $proxy, $arg, $size, $timeout) = @_;
  135.  
  136.     $size = 4096 unless $size;
  137.  
  138.     LWP::Debug::trace('()');
  139.  
  140.     # check proxy
  141.     if (defined $proxy)
  142.     {
  143.     return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  144.                    'You can not proxy through the ftp');
  145.     }
  146.  
  147.     my $url = $request->url;
  148.     if ($url->scheme ne 'ftp') {
  149.     my $scheme = $url->scheme;
  150.     return HTTP::Response->new(&HTTP::Status::RC_INTERNAL_SERVER_ERROR,
  151.                "LWP::Protocol::ftp::request called for '$scheme'");
  152.     }
  153.  
  154.     # check method
  155.     my $method = $request->method;
  156.  
  157.     unless ($method eq 'GET' || $method eq 'HEAD' || $method eq 'PUT') {
  158.     return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  159.                    'Library does not allow method ' .
  160.                    "$method for 'ftp:' URLs");
  161.     }
  162.  
  163.     if ($init_failed) {
  164.     return HTTP::Response->new(&HTTP::Status::RC_INTERNAL_SERVER_ERROR,
  165.                    $init_failed);
  166.     }
  167.  
  168.     my $host     = $url->host;
  169.     my $port     = $url->port;
  170.     my $user     = $url->user;
  171.     my $password = $url->password;
  172.  
  173.     # If a basic autorization header is present than we prefer these over
  174.     # the username/password specified in the URL.
  175.     {
  176.     my($u,$p) = $request->authorization_basic;
  177.     if (defined $u) {
  178.         $user = $u;
  179.         $password = $p;
  180.     }
  181.     }
  182.  
  183.     # We allow the account to be specified in the "Account" header
  184.     my $account = $request->header('Account');
  185.  
  186.     my $ftp = $self->_connect($host, $port, $user, $account, $password, $timeout);
  187.     return $ftp if ref($ftp) eq "HTTP::Response"; # ugh!
  188.  
  189.     # Create an initial response object
  190.     my $response = HTTP::Response->new(&HTTP::Status::RC_OK, "OK");
  191.     $response->header(Server => $ftp->http_server);
  192.     $response->header('Client-Request-Num' => $ftp->request_count);
  193.     $response->request($request);
  194.  
  195.     # Get & fix the path
  196.     my @path =  grep { length } $url->path_segments;
  197.     my $remote_file = pop(@path);
  198.     $remote_file = '' unless defined $remote_file;
  199.  
  200.     my $type;
  201.     if (ref $remote_file) {
  202.     my @params;
  203.     ($remote_file, @params) = @$remote_file;
  204.     for (@params) {
  205.         $type = $_ if s/^type=//;
  206.     }
  207.     }
  208.  
  209.     if ($type && $type eq 'a') {
  210.     $ftp->ascii;
  211.     }
  212.     else {
  213.     $ftp->binary;
  214.     }
  215.  
  216.     for (@path) {
  217.     LWP::Debug::debug("CWD $_");
  218.     unless ($ftp->cwd($_)) {
  219.         return HTTP::Response->new(&HTTP::Status::RC_NOT_FOUND,
  220.                        "Can't chdir to $_");
  221.     }
  222.     }
  223.  
  224.     if ($method eq 'GET' || $method eq 'HEAD') {
  225.     LWP::Debug::debug("MDTM");
  226.     if (my $mod_time = $ftp->mdtm($remote_file)) {
  227.         $response->last_modified($mod_time);
  228.         if (my $ims = $request->if_modified_since) {
  229.         if ($mod_time <= $ims) {
  230.             $response->code(&HTTP::Status::RC_NOT_MODIFIED);
  231.             $response->message("Not modified");
  232.             return $response;
  233.         }
  234.         }
  235.     }
  236.  
  237.     # We'll use this later to abort the transfer if necessary. 
  238.     # if $max_size is defined, we need to abort early. Otherwise, it's
  239.       # a normal transfer
  240.     my $max_size = undef;
  241.  
  242.     # Set resume location, if the client requested it
  243.     if ($request->header('Range') && $ftp->supported('REST'))
  244.     {
  245.         my $range_info = $request->header('Range');
  246.  
  247.         # Change bytes=2772992-6781209 to just 2772992
  248.         my ($start_byte,$end_byte) = $range_info =~ /.*=\s*(\d+)-(\d+)?/;
  249.         if ( defined $start_byte && !defined $end_byte ) {
  250.  
  251.           # open range -- only the start is specified
  252.  
  253.           $ftp->restart( $start_byte );
  254.           # don't define $max_size, we don't want to abort early
  255.         }
  256.         elsif ( defined $start_byte && defined $end_byte &&
  257.             $start_byte >= 0 && $end_byte >= $start_byte ) {
  258.  
  259.           $ftp->restart( $start_byte );
  260.           $max_size = $end_byte - $start_byte;
  261.         }
  262.         else {
  263.  
  264.           return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  265.              'Incorrect syntax for Range request');
  266.         }
  267.     }
  268.     elsif ($request->header('Range') && !$ftp->supported('REST'))
  269.     {
  270.         return HTTP::Response->new(&HTTP::Status::RC_NOT_IMPLEMENTED,
  271.              "Server does not support resume.");
  272.     }
  273.  
  274.     my $data;  # the data handle
  275.     LWP::Debug::debug("retrieve file?");
  276.     if (length($remote_file) and $data = $ftp->retr($remote_file)) {
  277.         my($type, @enc) = LWP::MediaTypes::guess_media_type($remote_file);
  278.         $response->header('Content-Type',   $type) if $type;
  279.         for (@enc) {
  280.         $response->push_header('Content-Encoding', $_);
  281.         }
  282.         my $mess = $ftp->message;
  283.         LWP::Debug::debug($mess);
  284.         if ($mess =~ /\((\d+)\s+bytes\)/) {
  285.         $response->header('Content-Length', "$1");
  286.         }
  287.  
  288.         if ($method ne 'HEAD') {
  289.         # Read data from server
  290.         $response = $self->collect($arg, $response, sub {
  291.             my $content = '';
  292.             my $result = $data->read($content, $size);
  293.  
  294.                     # Stop early if we need to.
  295.                     if (defined $max_size)
  296.                     {
  297.                       # We need an interface to Net::FTP::dataconn for getting
  298.                       # the number of bytes already read
  299.                       my $bytes_received = $data->bytes_read();
  300.  
  301.                       # We were already over the limit. (Should only happen
  302.                       # once at the end.)
  303.                       if ($bytes_received - length($content) > $max_size)
  304.                       {
  305.                         $content = '';
  306.                       }
  307.                       # We just went over the limit
  308.                       elsif ($bytes_received  > $max_size)
  309.                       {
  310.                         # Trim content
  311.                         $content = substr($content, 0,
  312.                           $max_size - ($bytes_received - length($content)) );
  313.                       }
  314.                       # We're under the limit
  315.                       else
  316.                       {
  317.                       }
  318.                     }
  319.  
  320.             return \$content;
  321.         } );
  322.         }
  323.         # abort is needed for HEAD, it's == close if the transfer has
  324.         # already completed.
  325.         unless ($data->abort) {
  326.         # Something did not work too well.  Note that we treat
  327.         # responses to abort() with code 0 in case of HEAD as ok
  328.         # (at least wu-ftpd 2.6.1(1) does that).
  329.         if ($method ne 'HEAD' || $ftp->code != 0) {
  330.             $response->code(&HTTP::Status::RC_INTERNAL_SERVER_ERROR);
  331.             $response->message("FTP close response: " . $ftp->code .
  332.                        " " . $ftp->message);
  333.         }
  334.         }
  335.     }
  336.     elsif (!length($remote_file) || ( $ftp->code >= 400 && $ftp->code < 600 )) {
  337.         # not a plain file, try to list instead
  338.         if (length($remote_file) && !$ftp->cwd($remote_file)) {
  339.         LWP::Debug::debug("chdir before listing failed");
  340.         return HTTP::Response->new(&HTTP::Status::RC_NOT_FOUND,
  341.                        "File '$remote_file' not found");
  342.         }
  343.  
  344.         # It should now be safe to try to list the directory
  345.         LWP::Debug::debug("dir");
  346.         my @lsl = $ftp->dir;
  347.  
  348.         # Try to figure out if the user want us to convert the
  349.         # directory listing to HTML.
  350.         my @variants =
  351.           (
  352.            ['html',  0.60, 'text/html'            ],
  353.            ['dir',   1.00, 'text/ftp-dir-listing' ]
  354.           );
  355.         #$HTTP::Negotiate::DEBUG=1;
  356.         my $prefer = HTTP::Negotiate::choose(\@variants, $request);
  357.  
  358.         my $content = '';
  359.  
  360.         if (!defined($prefer)) {
  361.         return HTTP::Response->new(&HTTP::Status::RC_NOT_ACCEPTABLE,
  362.                    "Neither HTML nor directory listing wanted");
  363.         }
  364.         elsif ($prefer eq 'html') {
  365.         $response->header('Content-Type' => 'text/html');
  366.         $content = "<HEAD><TITLE>File Listing</TITLE>\n";
  367.         my $base = $request->url->clone;
  368.         my $path = $base->path;
  369.         $base->path("$path/") unless $path =~ m|/$|;
  370.         $content .= qq(<BASE HREF="$base">\n</HEAD>\n);
  371.         $content .= "<BODY>\n<UL>\n";
  372.         for (File::Listing::parse_dir(\@lsl, 'GMT')) {
  373.             my($name, $type, $size, $mtime, $mode) = @$_;
  374.             $content .= qq(  <LI> <a href="$name">$name</a>);
  375.             $content .= " $size bytes" if $type eq 'f';
  376.             $content .= "\n";
  377.         }
  378.         $content .= "</UL></body>\n";
  379.         }
  380.         else {
  381.         $response->header('Content-Type', 'text/ftp-dir-listing');
  382.         $content = join("\n", @lsl, '');
  383.         }
  384.  
  385.         $response->header('Content-Length', length($content));
  386.  
  387.         if ($method ne 'HEAD') {
  388.         $response = $self->collect_once($arg, $response, $content);
  389.         }
  390.     }
  391.     else {
  392.         my $res = HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  393.               "FTP return code " . $ftp->code);
  394.         $res->content_type("text/plain");
  395.         $res->content($ftp->message);
  396.         return $res;
  397.     }
  398.     }
  399.     elsif ($method eq 'PUT') {
  400.     # method must be PUT
  401.     unless (length($remote_file)) {
  402.         return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  403.                        "Must have a file name to PUT to");
  404.     }
  405.     my $data;
  406.     if ($data = $ftp->stor($remote_file)) {
  407.         LWP::Debug::debug($ftp->message);
  408.         LWP::Debug::debug("$data");
  409.         my $content = $request->content;
  410.         my $bytes = 0;
  411.         if (defined $content) {
  412.         if (ref($content) eq 'SCALAR') {
  413.             $bytes = $data->write($$content, length($$content));
  414.         }
  415.         elsif (ref($content) eq 'CODE') {
  416.             my($buf, $n);
  417.             while (length($buf = &$content)) {
  418.             $n = $data->write($buf, length($buf));
  419.             last unless $n;
  420.             $bytes += $n;
  421.             }
  422.         }
  423.         elsif (!ref($content)) {
  424.             if (defined $content && length($content)) {
  425.             $bytes = $data->write($content, length($content));
  426.             }
  427.         }
  428.         else {
  429.             die "Bad content";
  430.         }
  431.         }
  432.         $data->close;
  433.         LWP::Debug::debug($ftp->message);
  434.  
  435.         $response->code(&HTTP::Status::RC_CREATED);
  436.         $response->header('Content-Type', 'text/plain');
  437.         $response->content("$bytes bytes stored as $remote_file on $host\n")
  438.  
  439.     }
  440.     else {
  441.         my $res = HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  442.                       "FTP return code " . $ftp->code);
  443.         $res->content_type("text/plain");
  444.         $res->content($ftp->message);
  445.         return $res;
  446.     }
  447.     }
  448.     else {
  449.     return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  450.                    "Illegal method $method");
  451.     }
  452.  
  453.     $response;
  454. }
  455.  
  456. 1;
  457.  
  458. __END__
  459.  
  460. # This is what RFC 1738 has to say about FTP access:
  461. # --------------------------------------------------
  462. #
  463. # 3.2. FTP
  464. #
  465. #    The FTP URL scheme is used to designate files and directories on
  466. #    Internet hosts accessible using the FTP protocol (RFC959).
  467. #
  468. #    A FTP URL follow the syntax described in Section 3.1.  If :<port> is
  469. #    omitted, the port defaults to 21.
  470. #
  471. # 3.2.1. FTP Name and Password
  472. #
  473. #    A user name and password may be supplied; they are used in the ftp
  474. #    "USER" and "PASS" commands after first making the connection to the
  475. #    FTP server.  If no user name or password is supplied and one is
  476. #    requested by the FTP server, the conventions for "anonymous" FTP are
  477. #    to be used, as follows:
  478. #
  479. #         The user name "anonymous" is supplied.
  480. #
  481. #         The password is supplied as the Internet e-mail address
  482. #         of the end user accessing the resource.
  483. #
  484. #    If the URL supplies a user name but no password, and the remote
  485. #    server requests a password, the program interpreting the FTP URL
  486. #    should request one from the user.
  487. #
  488. # 3.2.2. FTP url-path
  489. #
  490. #    The url-path of a FTP URL has the following syntax:
  491. #
  492. #         <cwd1>/<cwd2>/.../<cwdN>/<name>;type=<typecode>
  493. #
  494. #    Where <cwd1> through <cwdN> and <name> are (possibly encoded) strings
  495. #    and <typecode> is one of the characters "a", "i", or "d".  The part
  496. #    ";type=<typecode>" may be omitted. The <cwdx> and <name> parts may be
  497. #    empty. The whole url-path may be omitted, including the "/"
  498. #    delimiting it from the prefix containing user, password, host, and
  499. #    port.
  500. #
  501. #    The url-path is interpreted as a series of FTP commands as follows:
  502. #
  503. #       Each of the <cwd> elements is to be supplied, sequentially, as the
  504. #       argument to a CWD (change working directory) command.
  505. #
  506. #       If the typecode is "d", perform a NLST (name list) command with
  507. #       <name> as the argument, and interpret the results as a file
  508. #       directory listing.
  509. #
  510. #       Otherwise, perform a TYPE command with <typecode> as the argument,
  511. #       and then access the file whose name is <name> (for example, using
  512. #       the RETR command.)
  513. #
  514. #    Within a name or CWD component, the characters "/" and ";" are
  515. #    reserved and must be encoded. The components are decoded prior to
  516. #    their use in the FTP protocol.  In particular, if the appropriate FTP
  517. #    sequence to access a particular file requires supplying a string
  518. #    containing a "/" as an argument to a CWD or RETR command, it is
  519. #    necessary to encode each "/".
  520. #
  521. #    For example, the URL <URL:ftp://myname@host.dom/%2Fetc/motd> is
  522. #    interpreted by FTP-ing to "host.dom", logging in as "myname"
  523. #    (prompting for a password if it is asked for), and then executing
  524. #    "CWD /etc" and then "RETR motd". This has a different meaning from
  525. #    <URL:ftp://myname@host.dom/etc/motd> which would "CWD etc" and then
  526. #    "RETR motd"; the initial "CWD" might be executed relative to the
  527. #    default directory for "myname". On the other hand,
  528. #    <URL:ftp://myname@host.dom//etc/motd>, would "CWD " with a null
  529. #    argument, then "CWD etc", and then "RETR motd".
  530. #
  531. #    FTP URLs may also be used for other operations; for example, it is
  532. #    possible to update a file on a remote file server, or infer
  533. #    information about it from the directory listings. The mechanism for
  534. #    doing so is not spelled out here.
  535. #
  536. # 3.2.3. FTP Typecode is Optional
  537. #
  538. #    The entire ;type=<typecode> part of a FTP URL is optional. If it is
  539. #    omitted, the client program interpreting the URL must guess the
  540. #    appropriate mode to use. In general, the data content type of a file
  541. #    can only be guessed from the name, e.g., from the suffix of the name;
  542. #    the appropriate type code to be used for transfer of the file can
  543. #    then be deduced from the data content of the file.
  544. #
  545. # 3.2.4 Hierarchy
  546. #
  547. #    For some file systems, the "/" used to denote the hierarchical
  548. #    structure of the URL corresponds to the delimiter used to construct a
  549. #    file name hierarchy, and thus, the filename will look similar to the
  550. #    URL path. This does NOT mean that the URL is a Unix filename.
  551. #
  552. # 3.2.5. Optimization
  553. #
  554. #    Clients accessing resources via FTP may employ additional heuristics
  555. #    to optimize the interaction. For some FTP servers, for example, it
  556. #    may be reasonable to keep the control connection open while accessing
  557. #    multiple URLs from the same server. However, there is no common
  558. #    hierarchical model to the FTP protocol, so if a directory change
  559. #    command has been given, it is impossible in general to deduce what
  560. #    sequence should be given to navigate to another directory for a
  561. #    second retrieval, if the paths are different.  The only reliable
  562. #    algorithm is to disconnect and reestablish the control connection.
  563.